/****************************************************************/
/*																*/
/*  XmlDocument.cpp												*/
/*																*/
/*  Implementation of the CXmlDocument class.					*/
/*	The class has a parser for XML file							*/
/*																*/
/*																*/
/****************************************************************/

#include "stdafx.h"
#include "XmlDocument.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

/********************************************************************/
/*																	*/
/* Function name : CXmlDocument										*/
/* Description   : Constructor										*/
/*																	*/
/********************************************************************/
CXmlDocument::CXmlDocument()
{
}

/********************************************************************/
/*																	*/
/* Function name : ~CXmlDocument									*/
/* Description   : Destructor										*/
/*																	*/
/********************************************************************/
CXmlDocument::~CXmlDocument()
{

}

/********************************************************************/
/*																	*/
/* Function name : DeleteContents									*/
/* Description   : Initialize variables to their initial values.	*/
/*																	*/
/********************************************************************/
void CXmlDocument::DeleteContents()
{
    // clean up any previous data
    while(!m_RootElement.m_ChildElements.IsEmpty())
    {
        delete m_RootElement.m_ChildElements.RemoveHead();
    }
    m_pCurElement = &m_RootElement;
    m_pCurElement->m_pParentElement = NULL;
    m_RootElement.m_strName = "";
    m_RootElement.m_strData = "";
    m_RootElement.m_strAttributes = "";
    m_RootElement.m_strFind = "";
    m_RootElement.m_posFind = NULL;
}


/********************************************************************/
/*																	*/
/* Function name : Parse											*/
/* Description   : Parse XML data.									*/
/*																	*/
/********************************************************************/
BOOL CXmlDocument::Parse(LPCTSTR lpszString)
{
    // clean previous document data
    DeleteContents();

    // save string
    m_strXml = lpszString;

    BOOL bInsideTag = FALSE;

    CString strTag, strData, strResult;

    for(int i=0; i<m_strXml.GetLength(); i++)
    {
        TCHAR ch = m_strXml[i];

        // begin of tag ?
        if(ch == '<')
        {
            strTag += ch;

            // add data to element
            m_pCurElement->m_strData = strData;
            // trim spaces
            m_pCurElement->m_strData.TrimLeft();
            m_pCurElement->m_strData.TrimRight();

            // clear data
            strData = "";

            // processing tag...
            bInsideTag = TRUE;
            continue;        
        }
        // end of tag ?
        if(ch == '>')
        {
            strTag += ch;

            // determine type and name of the tag
            int nType = ValidateTag(strTag, strResult);

            // clear tag
            strTag = "";

            // skip errors/comments/declaration
            if (nType == -1)
            {
                continue;
            }

            // start or start-end tag -> add new element
            if(nType == 0 || nType == 2)
            {
                // currently processing root element ?
                if (m_RootElement.m_strName.IsEmpty())
                {
                    // split name and attributes
                    int nPos = strResult.Find(' ');
                    if (nPos != -1)
                    {
                        // set properties of root element
                        m_RootElement.m_strName = strResult.Left(nPos);
                        m_RootElement.m_strAttributes = strResult.Mid(nPos+1);
                        // trim spaces
                        m_RootElement.m_strAttributes.TrimLeft();
                        m_RootElement.m_strAttributes.TrimRight();
                    }
                    else
                    {
                        m_RootElement.m_strName = strResult;
                    }
                }
                else
                {
                    // create new element
                    CXmlElement *pElement = new CXmlElement;

                    pElement->m_pParentElement = m_pCurElement;

                    // split name and attributes
                    int nPos = strResult.Find(' ');
                    if (nPos != -1)
                    {
                        // set properties of current element
                        pElement->m_strName = strResult.Left(nPos);
                        pElement->m_strAttributes = strResult.Mid(nPos+1);
                        // trim spaces
                        pElement->m_strAttributes.TrimLeft();
                        pElement->m_strAttributes.TrimRight();
                    }
                    else
                    {
                        pElement->m_strName = strResult;
                    }
                    m_pCurElement->m_ChildElements.AddTail(pElement);
                    m_pCurElement = pElement;
                }
            }

            // end or start-end tag -> finished with current tag
            if(nType == 1 || nType == 2)
            {
                // go back to parent level
                if (m_pCurElement->m_pParentElement != NULL)
                    m_pCurElement = m_pCurElement->m_pParentElement;
            }

            // processing data...
            bInsideTag = FALSE;
            continue;
        }

        if(bInsideTag)
        {
            // append character to tag
            strTag += ch;
        }
        else
        {
            // append character to data
            strData += ch;
        }
    }
    return TRUE;
}


/********************************************************************/
/*																	*/
/* Function name : ValidateTag										*/
/* Description   : Determine type and name of the tag.				*/
/*				   0 = start tag									*/
/*				   1 = end tag										*/
/*				   2 = start-end tag								*/
/*				   -1 = comments or declaration						*/
/*																	*/
/********************************************************************/
int CXmlDocument::ValidateTag(CString &strTag, CString &strResult)
{
    strResult = "";

    if (strTag.IsEmpty())
        return -1;

    TCHAR ch;
    TCHAR chPrevious = '0';

    int nResult = 0;
    int nCount = 0;

    // determine tag type
    while(nCount <strTag.GetLength())
    {
        // get next character
        ch = strTag[nCount++];

        // skip comments '<!' and declaration '<?'
        if ((chPrevious == '<' && ch == '!') || 
            (chPrevious == '<' && ch == '?'))
        {
            return -1;
        }
        else
            // is it an end-tag '</' ?
            if(chPrevious =='<' && ch == '/') 
            {
                nResult = 1;
            }
            else
                // is it a start-end-tag '<..../>' ?
                if(chPrevious =='/' && ch == '>') 
                {
                    nResult = 2;
                    // remove last character
                    strResult.Delete(strResult.GetLength()-1, 1);
                }
                else 
                    if(ch != '<' && ch != '>')
                    {
                        // add character
                        strResult += ch;
                    }
                    chPrevious = ch;
    }
    return nResult;
}



/********************************************************************/
/*																	*/
/* Function name : GetFirstChild									*/
/* Description   : Get first child of element.						*/
/*																	*/
/********************************************************************/
CXmlElement *CXmlDocument::GetFirstChild(CXmlElement *pElement) 
{
    pElement->m_posFind = NULL;

    POSITION pos = pElement->m_ChildElements.GetHeadPosition();
    if (pos != NULL)
    {
        CXmlElement *pResult = (CXmlElement *)pElement->m_ChildElements.GetNext(pos);
        pElement->m_posFind = pos;
        return pResult;
    }
    return NULL;
}


/********************************************************************/
/*																	*/
/* Function name : GetNextSibling									*/
/* Description   : Get next child of specified element.				*/
/*																	*/
/********************************************************************/
CXmlElement *CXmlDocument::GetNextSibling(CXmlElement *pElement) 
{
    if (pElement->m_posFind)
        return (CXmlElement *)pElement->m_ChildElements.GetNext(pElement->m_posFind);
    else
        return NULL;
}


/********************************************************************/
/*																	*/
/* Function name : FindElement										*/
/* Description   : Find first occurence of specified tag.			*/
/*																	*/
/********************************************************************/
CXmlElement *CXmlDocument::FindElement(CXmlElement *pElement, LPCTSTR lpszName) 
{
    pElement->m_posFind = NULL;

    pElement->m_strFind = lpszName;

    POSITION pos = pElement->m_ChildElements.GetHeadPosition();
    while (pos != NULL)
    {
        CXmlElement *pResult = (CXmlElement *)pElement->m_ChildElements.GetNext(pos);
        if (pResult->m_strName.CompareNoCase(lpszName) == 0)
        {
            pElement->m_posFind = pos;
            return pResult;
        }

    }

    return NULL;
}


/********************************************************************/
/*																	*/
/* Function name : FindNextElement									*/
/* Description   : Find next occurence of specified tag				*/
/*																	*/
/********************************************************************/
CXmlElement *CXmlDocument::FindNextElement(CXmlElement *pElement) 
{
    while(pElement->m_posFind != NULL)
    {
        CXmlElement *pResult = (CXmlElement *)pElement->m_ChildElements.GetNext(pElement->m_posFind);
        if (pResult->m_strName.CompareNoCase(pElement->m_strFind) == 0)
        {
            return pResult;
        }
    }
    return NULL;
}
/********************************************************************/
/*																	*/
/* Function name : SetXmlFile										*/
/* Description   : Set document from XML file						*/
/*																	*/
/********************************************************************/
BOOL CXmlDocument::SetXmlFile(LPCTSTR lpszFileName)
{

    //CString strXML;	
    //ULONGLONG ullLength=0;

    //try
    //{
    //    CFile inFile;
    //    //xml file open in read only mode
    //    inFile.Open(lpszFileName,CFile::modeRead);
    //    ullLength = inFile.GetLength();
    //    //read xml file
    //    //inFile.Read(strXML.GetBufferSetLength(ullLength),ullLength);
    //    inFile.Read(&strXML, ullLength);

    CString strXML;	
    long ullLength=0;

    try
    {
        CFile inFile;
        //xml file open in read only mode
        inFile.Open(lpszFileName, CFile::modeRead);
        ullLength = inFile.GetLength();
        //read xml file
        //char pbuf[ullLength];

        char *pBuf = new char[ullLength];
        inFile.Read(pBuf, ullLength);
        strXML = pBuf;
        delete[] pBuf;
        inFile.Close();
        //call Parse function for parsing xml string
        return Parse(strXML);
    }
    catch(CFileException *ex)
    {
        ex->Delete();
        return FALSE;
    }

    // remove endofline and tabs
    strXML.Remove('\n');
    strXML.Remove('\r');
    strXML.Remove('\t');

    return Parse(strXML);
}
